home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Monster Media 1996 #14
/
Monster Media No. 14 (April 1996) (Monster Media, Inc.).ISO
/
prog_d
/
oleauttr.zip
/
OLEAUTO.TXT
< prev
next >
Wrap
Text File
|
1996-01-04
|
24KB
|
556 lines
OLEAUTO.TXT
Supplementary documentation for TOLEAutoClient component
************************************************************
For support, send E-mail to:
sraike@iconz.co.nz
or CompuServe: 100236,1656
or send FAX to: 64-9-832-0088
************************************************************
This file describes how to write a "mirror" class for Delphi that
lets your application access properties and methods of an OLE Automation
server just as if they were properties and methods of one of your
own objects.
Since TOleAutoClient was first released in April 1995, filling
the gap left by Delphi's lack of OLE Automation support, customers
have used it to control a wide variety of OLE Automation servers
including Microsoft Office products, ABC Flowcharter, Visio, Acrobat,
Watermark and a number of others. (All these names are the property
of their respective trademark owners.)
One description here uses Microsoft Word for Windows 6.0 as a typical
OLE Automation server. "Word", "Excel", "Word for Windows", "Windows"
and "Visual Basic" are registered trademarks of Microsoft Corporation.
For more information, refer to the README.1ST and OLEAUTO.INT files
and to the accompanying compressed example files like WORDDRIV.ZIP,
NUMSVR.ZIP and VISIOOLE.ZIP that contain working sample code.
Many servers "expose" a hierarchy of nested OLE objects and classes.
After reading the introductory material in this file, you should refer
to the file NESTOBJ.TXT for a discussion of how to write mirror classes
that describe such nested objects. You should also refer to the
WHATSNEW.TXT file for a description of the features incorporated
in the latest release of TOLEAutoClient.
******************************************
OVERVIEW
******************************************
Some Windows development tools (Microsoft Visual Basic, for
one) let you control OLE Automation servers (like Microsoft Word 6.0,
for example). An OLE Automation server "exposes" certain properties
and methods for access by other programs. Microsoft Word 6.0 exposes
most of its WordBasic macro language; other servers expose properties,
objects and methods as detailed in their documentation. For Word 6.0,
you should refer to Microsoft documentation such as its online Help
and the Microsoft Office Developers' Kit (ODK).
For example, among the WordBasic methods Word exposes are:
FileNew - tells Word to open a new file
ToolsOptionsEdit - sets Tools|Edit|Options dialog items
ToolsMacro - tells Word to run a template macro
FileClose - tells Word to close a current file
******************************************
EXAMPLE 1 - (WORDDRIV.ZIP)
******************************************
Suppose you want your program to tell Word to open a new file based
on the NORMAL.DOT template, set and clear some check boxes on
the Edit tab of the Tools|Options dialog, run a WordBasic macro
called DummyMsg that is stored in the NORMAL.DOT template, and then
close the file without saving it. Referring to the Word documentation,
you discover that the WordBasic methods you need to call, with their
parameters, are:
FileNew "NORMAL.DOT"
ToolsOptionsEdit 1,1,0,0,0,0,0,0,0
ToolsMacro "DummyMsg", 1
FileClose 2
(WordBasic actually allows you to use named arguments like .Run = 1
instead of "positional" arguments; neither TOLEAutoClient
nor Visual Basic yet supports named arguments. Also, be aware that
WordBasic online Help sometimes fails to list the named arguments
in their correct positional order; this information is given in
the file POSITION.TXT in the Office Developers' Kit. Alternatively,
you can record a WordBasic macro and then look at the macro code it
generates for the correct parameter order.)
If you were writing Visual Basic code to do this, it would look like:
Dim ob As Object
Set ob = CreateObject("word.basic")
ob.FileNew "NORMAL.DOT"
ob.ToolsOptionsEdit 1,1,0,0,0,0,0,"Microsoft Word"
ob.ToolsMacro "DummyMsg", 1
ob.FileClose 2
Set ob = Nothing
...
Using the TOleAutomationClient component, you'll write the following
code in Delphi to accomplish the same thing:
var
WordOb : TWordObj;
begin
WordOb := TWordObj.CreateObject('word.basic');
WordOb.FileNew('NORMAL.DOT');
WordOb.ToolsOptionsEdit; { The example has hard-coded options...}
WordOb.ToolsMacro('DummyMsg'); { Ditto for the second argument...}
WordOb.FileClose(2);
WordOb.Release;
...
But there's a catch! (Read on -- it has to do with the TWordObj class.)
Since Delphi is a compiled language, before you can call a method
or refer to a property for an object you need to let Delphi know that
the object belongs to a class that contains that method or property.
The way to do that in Delphi is to create a new unit and in it to declare
a new class with the needed methods and properties. Fortunately, the
TOleAutoClient component simplifies the process by letting you
derive the class from a parent class it supplies: TOleObject. In the case
of the example given above, the interface section of the unit looks like:
unit WordCls;
interface
uses
SysUtils, OleAuto; { OLEAUTO.DCU must be in \DELPHI\LIB }
type
TWordObj = class(TOleObject) {TOleObject is defined in OLEAUTO.DCU}
public
procedure FileNew(TemplateName : String);
procedure ToolsMacro(MacroName : String);
procedure FileClose(CloseMode : Integer);
procedure ToolsOptionsEdit; {arguments omitted for simplicity}
end;
So that these methods can communicate with Word through OLE Automation,
in the implementation section of the WordCls unit you define the class
methods you just declared, inserting calls to methods in the parent
TOleObject class that pass each of the method arguments and define their
types, and that perform the actual OLE method calls. Refer to the file
OLEAUTO.INT for a description of the allowable types.
In the implementation of a method call, you need to do two things:
1. Pass each argument and define its type. To do this, you
call SetOleMethodArg (a member of the parent TOLEObject class)
once for each argument to be passed. The order in which
the arguments are passed is FROM RIGHT TO LEFT!
2. Have OLE call the method in the server application. To do this,
you call either CallOleFunction or CallOleProc, according to
whether or not the server method returns a value.
In this example, none of the methods returns a value; in the next ones
you'll see how to call a method that does return a value, and how to
access properties (in addition to methods) exposed by a server.
The implementation section looks like this:
implementation
procedure TWordObj.ToolsOptionsEdit;
const
pszMSW : PChar = 'Microsoft Word';
var
x : Integer;
begin
{ It's necessary to supply ALL the arguments to ToolsOptionsEdit. }
{ We always set the arguments in reverse order: i.e., right to left. }
{
For this WordBasic command, all the options indicate check-box status
values (0 = unchecked) in the Word 6 Tools|Options\Edit dialog.
}
SetOleMethodArg('PChar', pszMSW); { .PictureEditor }
{ The following are just some sample option settings. }
x := 0; { Unchecked. }
SetOleMethodArg('Integer', x); { .AllowAccentedUppercase }
SetOleMethodArg('Integer', x); { .SmartCutPaste }
SetOleMethodArg('Integer', x); { .Overtype }
SetOleMethodArg('Integer', x); { .InsForPaste }
SetOleMethodArg('Integer', x); { .AutoWordSelection }
x := 1; { Checked. }
SetOleMethodArg('Integer', x); { .DragAndDrop }
SetOleMethodArg('Integer', x); { .ReplaceSelection }
{After setting the arguments, call the method. This method
doesn't return a value, so we use CallOleProc; if it did return
a value, we'd use CallOleFunction.}
CallOleProc('ToolsOptionsEdit');
end;
procedure TWordObj.FileNew(TemplateName : String);
var
pszName : PChar;
begin
pszName := StrAlloc(256); { Get some string space. }
StrPLCopy(pszName, TemplateName, 40);
{ Tell Word the name of the template. }
SetOleMethodArg('PChar', pszName);
CallOleProc('FileNew');
StrDispose(pszName);
end;
procedure TWordObj.ToolsMacro(MacroName : String);
var
mode : Integer;
pszName : PChar;
begin
pszName := StrAlloc(256); { Get some string space. }
mode := 1;
{ Tell Word to .Run the macro. }
{ You might want to write a sample Word
macro and save it in the NORMAL.DOT template, perhaps something like:
Sub Main
MsgBox "Clicked me!"
End Sub
}
{ Set the arguments from right to left.}
SetOleMethodArg('Integer', mode); { This arg *MUST* be present, even if its type is '' }
StrPLCopy(pszName, MacroName, 40);
SetOleMethodArg('PChar', pszName); { WordBasic macro name }
{ Execute it. }
CallOleProc('ToolsMacro');
StrDispose(pszName);
end;
procedure TWordObj.FileClose(CloseMode : Integer);
begin
SetOleMethodArg('Integer', CloseMode);
CallOleProc('FileClose');
end;
end.
******************************************
IN-PROCESS SERVERS AND LOCAL SERVERS
******************************************
See the file WHATSNEW.TXT for a discussion of the support provided
for controlling in-process OLE Automation servers.
******************************************
CONTROLLING "EMBEDDED" OLE OBJECTS AND
ALREADY-RUNNING INSTANCES
******************************************
See the file WHATSNEW.TXT for a discussion of the support provided
for this topic.
******************************************
MORE INFORMATION ON ARGUMENT TYPES
******************************************
Internally, all parameters to OLE methods are actually passed as
VARIANT types, directly analogous to the Variant type in Microsoft
Visual Basic. This type is declared within the OLEAUTO.DCU unit,
along with a number of OLE DLL routines for manipulating VARIANT
quantities. However, when you pass arguments using SetOleMethodArg,
and when you call OLE server methods that return a value, or when you
get and set properties of OLE objects, you need to pass a string
denoting the type of the argument or return value. See the file
OLEAUTO.INT for a list of the allowable Delphi types.
You can pass arguments to methods both by value and by reference; a
reference argument is equivalent to one declared as var in Pascal.
In order to pass an argument by reference, you prefix the name of its
type (the string passed as the first parameter to SetOleMethodArg) with
the symbol "&". Most servers will expect their arguments to be passed
by value, but there are cases when they will accept arguments passed
by reference. For example, an OLE (server) method may be designed to
modify the values of one or more of its arguments.
In order to pass an argument defined as a user-defined type, OLE
requires you to pass a pointer to the argument instead. In this case,
you should cast the pointer to a generic pointer (i.e., Pointer(myptr))
before passing it with SetOleMethodArg as an argument of type 'Pointer'.
The _only_ type names allowed for the first parameter to the
SetOleMethodArg method are those listed in OLEAUTO.INT. The same
is true for the allowable return types of methods returning values
(you specify the return type in the second parameter to
CallOleFunction) and for retrieving and setting properties
with the GetOleProperty and SetOleProperty methods.
Many servers define and "expose" a hierarchy of "nested" objects,
collections, etc. Examples are Excel, Visio (TM), etc. To access
these nested objects, often the servers provide methods or properties
whose type is a kind of object pointer. In some cases, the server
documentation describes the type returned by such methods/properties
as "LPDISPATCH", or "PDISP", or a similar type. With the TOleAutoClient
component, the type name you use for this purpose is 'pInterface'; it
means essentially the same thing as LPDISPATCH, which is one of the
type names often used in C/C++ language documentation of OLE automation.
******************************************
EXAMPLE 2 - (NUMSVR.ZIP)
******************************************
As another example, let's look at a small OLE Automation server
called NUMSVR1.EXE, distributed along with this component. This server
simulates an integer property by exposing read and write methods GetX
and SetX, and one method, named Incr, that accepts a single integer
parameter PASSED BY REFERENCE. The Incr() method's only effect is to
increment its argument.
In the NumSvr example program, you can watch how your Delphi program
passes an integer variable to the server which ends up being incremented
as a result of the call to Incr(). This is a useful feature not (yet)
supported by Visual Basic.
The "mirror" class TNumSvrOb is contained in the NUMSVCLS.PAS unit;
its interface section exactly reflects the OLE server's exposed
methods, and the implementation is straightforward. Notice how
the argument to the Incr method is passed via SetOleMethodArg with a
type name of '&integer' to indicate that the argument is being passed
by reference rather than by value. Also notice how the
function GetX is implemented. Here the call to CallOleFunction
specifies the name of the OLE method ('GetX'), the return type ('integer')
and the name of the variable into which the return value is to be put.
unit Numsvcls;
interface
uses
SysUtils, OleAuto;
type
TNumSvrOb = class(TOleObject)
private
procedure SetX(newx : Integer);
function GetX : Integer;
public
property X : integer read GetX write SetX;
procedure Incr(var x : Integer);
end;
implementation
procedure TNumSvrOb.Incr(var x : Integer);
begin
{ Specify argument type as '&Integer' to pass by reference. }
SetOleMethodArg('&Integer', x);
CallOleProc('Incr');
end;
procedure TNumSvrOb.SetX(newx : Integer);
begin
SetOleMethodArg('integer', newx);
CallOleProc('SetX');
end;
function TNumSvrOb.GetX : Integer;
var
x : Integer;
begin
CallOleFunction('GetX', 'Integer', x);
Result := x;
end;
end.
******************************************
ERROR CHECKING AND HANDLING
******************************************
++++++++++++++++++++++++++++++
Inability to start up a server
++++++++++++++++++++++++++++++
The CreateObject and CreateInProcessObject constructors
for your mirror class, inherited from TOleObject, will raise
an EOleAutoNoCreate exception if an OLE server cannot be started up
successfully. This can happen in the following situations:
a) The ProgID string that identifies the server object can't
be found in your system registry (REG.DAT under Windows 3.x).
You can inspect the registry by running REGEDIT /V from
Program Manager. This usually means that the server has
never registered itself, but may mean that the registry
has become corrupted. With many OLE Automation servers,
you need to run the server application under Windows at
least once as a stand-alone application so that it can
register itself. Alternatively, some servers come with
setup or installation programs that accomplish this task.
b) Windows can't locate the executable file or DLL
containing the server in the place it expects to find it.
This may mean that the server application has been moved
or re-installed into a different directory than the one
from which it was originally installed or registered.
Re-installing the server application will correct
this problem.
c) Either Windows itself or one of its OLE support DLLs
encountered an error trying to start up the server application.
This can occur as a result of a bug in the server itself, or
may be due to a system failure, Windows GPF, etc. during the
debugging process. It is also possible that the versions
of the Windows OLE system DLLs (normally installed by Windows
in \WINDOWS\SYSTEM) are obsolete; in particular, the versions
of COMPOBJ.DLL, OLE2DISP.DLL and OLE2.DLL should be no
earlier than version 2.02.
d) Programmers sometimes forget to place a TOleAutoClient
component on a form whose unit uses the component. The
application will compile correctly, but since Delphi will never
call the TOLEAutoClient constructor, the Windows OLE libraries
will never be correctly initialised. Mysteriously, although
this bug can happen under Windows 3.x, the application will
run successfully under Windows 95!
You can handle the EOleAutoNoCreate exception yourself with a try block
or let Delphi handle it for you.
Refer to the file WHATSNEW.TXT for additional information
regarding exceptions raised by the GetObject mirror class constructor
used to "hook into" and control a running instance of an OLE Automation
server.
If there is insufficient memory to complete a SetOleMethodArg call,
typically with a string argument, the TOleAutomationClient component will
raise an EOleAutoOutOfMemory exception. In the unlikely event this occurs,
you can handle the exception yourself or let Delphi handle it.
+++++++++++++++++++++++++++++++++++++++
OLE method calls don't execute properly
+++++++++++++++++++++++++++++++++++++++
Attempts to retrieve or set a server's properties or to call its methods
will not generate exceptions if they fail. The most common situation in
which this will occur is when there is a type mismatch between the mirror
class and the server, either for a property or a method argument or return
type. Some servers will attempt to coerce an argument to the correct type,
but not all. Also, some server methods may be incorrectly documented as
functions returning a type of VT_EMPTY (corresponding to a Delphi type
string of '', an empty string) when they really should be implemented as
procedures with no return value. Finally, some methods may allow "optional"
arguments to be omitted, while others may require all specified arguments
to be present and will cause en error if the number of arguments is
incorrect. Check your server documentation carefully (if there is any).
The only argument/method/property types supported by TOLEAutoClient are
those documented in the OLEAUTO.INT file. Note also that the PInterface
type referred to in this documentation may be referred to as "pIDisp",
"pDisp", "pIDispatch" or "LPDISPATCH" in some server documentation.
Your mirror class should be written to pass 'pInterface' as a type name
in such cases.
You can check for success or failure of a call to any of CallOleProc,
CallOleFunction, GetOleProperty or SetOleProperty by a call to the
GetOleMethodFailFlag method, declared in the TOleObject class as
function TOleObject.GetOleAutoFailFlag : Boolean;
This returns an internal flag which will be True if the call failed (i.e.,
if the Windows OLE DLLs returned with an error indication) or False if
the call was executed successfully. An alternative and equivalent
way to check this status is to refer to the OleAutoFailFlag property
of your mirror class object descended from TOleObject.
For more detailed information regarding the nature of an error
generated during one of the above calls, you can access the
OleAutoErrCode property (of type HRESULT) of your mirror class
object descended from TOleObject. If there has been no error,
this property should have the value S_OK. Refer to the OLEAUTO.INT
file for details of the error codes returned through this property.
Alternatively, if you prefer to use Delphi's exception-handling
mechanism to signal failure of OLE method calls or property accesses,
the code given below makes this easy to accomplish. You simply
derive an intermediate class, say TOleObjectEx, from TOleObject
and use this intermediate class instead of TOleObject as the
ancestor from which you derive your mirror class. Working code
for this class appears below. You can place it in a new unit
called OLEAUTX.PAS that you add to your project. Be sure to
replace OLEAUTO with OLEAUTX in the Uses clause of your mirror
class unit so your mirror class can "see" TOleObjectEx. Note
that the code below displays exception messages that don't
depend on the specific value of OleAutoErrCode. Feel free
to enhance the logic shown below, if you like, by comparing
the value of OleAutoErrCode to the various E_xxxxx error code
values defined in OLEAUTO.INT and varying the exception
messages accordingly.
{
OLEAUTX.PAS (C) 1995 W. Raike
}
unit OleAutX;
{*******************************************************************}
{
Class TOLEAutoEx derived from TOleObject overrides virtual methods
for OLE property/method access in order to raise an exception when
such a method call is unsuccessful.
}
{*******************************************************************}
interface
uses
OleAuto;
type
TOleObjectEx = class(TOleObject)
procedure CallOleFunction(sMethodName : String;
sReturnType : String; var Retval); override;
procedure CallOleProc(sMethodName : String); override;
procedure GetOleProperty(sPropertyName : String;
sPropertyType : String; var RetVal); override;
procedure SetOleProperty(sPropertyName : String;
sPropertyType : String; var PropVal); override;
end;
implementation
procedure TOleObjectEx.CallOleFunction(sMethodName : String;
sReturnType : String; var Retval);
begin
inherited CallOleFunction(sMethodName, sReturnType, Retval);
if OleAutoErrCode <> S_OK then
raise EOleAutoInvokeFailure.Create('OLE function call failed');
end;
procedure TOleObjectEx.CallOleProc(sMethodName : String);
begin
inherited CallOleProc(sMethodName);
if OleAutoErrCode <> S_OK then
raise EOleAutoInvokeFailure.Create('OLE procedure call failed');
end;
procedure TOleObjectEx.GetOleProperty(sPropertyName : String;
sPropertyType : String; var RetVal);
begin
inherited GetOleProperty(sPropertyName, sPropertyType, Retval);
if OleAutoErrCode <> S_OK then
raise EOleAutoInvokeFailure.Create('OLE property get failed');
end;
procedure TOleObjectEx.SetOleProperty(sPropertyName : String;
sPropertyType : String; var PropVal);
begin
inherited SetOleProperty(sPropertyName, sPropertyType, PropVal);
if OleAutoErrCode <> S_OK then
raise EOleAutoInvokeFailure.Create('OLE property set failed');
end;
end.